Day 12: Polymorphism and Casting
Polymorphism is the ability for a variable of the same type to refer to objects of various forms. It’s like the word “animal” being able to refer to a dog, cat, or bird. This allows you to write flexible and extensible code.
Upcasting and Polymorphism
Storing a child object in a parent type variable is called upcasting. It happens automatically.
class Animal {
String name;
Animal(String name) {
this.name = name;
}
void speak() {
System.out.println(name + " makes a sound.");
}
void eat() {
System.out.println(name + " is eating.");
}
}
class Dog extends Animal {
Dog(String name) { super(name); }
@Override
void speak() {
System.out.println(name + ": Woof woof!");
}
void fetch() {
System.out.println(name + " fetches the ball.");
}
}
class Cat extends Animal {
Cat(String name) { super(name); }
@Override
void speak() {
System.out.println(name + ": Meow~");
}
void scratch() {
System.out.println(name + " extends its claws.");
}
}
class Bird extends Animal {
Bird(String name) { super(name); }
@Override
void speak() {
System.out.println(name + ": Tweet tweet!");
}
void fly() {
System.out.println(name + " flies into the sky.");
}
}
public class PolymorphismBasic {
public static void main(String[] args) {
// Upcasting: referencing child objects as parent type
Animal animal1 = new Dog("Buddy");
Animal animal2 = new Cat("Whiskers");
Animal animal3 = new Bird("Tweety");
// Polymorphism: same method call, but different behavior depending on the actual object
animal1.speak(); // Buddy: Woof woof!
animal2.speak(); // Whiskers: Meow~
animal3.speak(); // Tweety: Tweet tweet!
// Using polymorphism with arrays
Animal[] animals = {animal1, animal2, animal3};
for (Animal animal : animals) {
animal.speak(); // Each animal's unique sound
animal.eat(); // Common method
}
// animal1.fetch(); // Compile error! (Animal type doesn't have fetch)
}
}
Downcasting and instanceof
Explicit casting is required when converting a parent type variable to a child type.
public class DowncastingExample {
static void handleAnimal(Animal animal) {
// Common behavior
animal.speak();
// Check actual type with instanceof, then downcast
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.fetch(); // Can use Dog-specific method
} else if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.scratch();
} else if (animal instanceof Bird) {
Bird bird = (Bird) animal;
bird.fly();
}
}
// Java 16+: Pattern matching instanceof
static void handleAnimalModern(Animal animal) {
animal.speak();
// Type check and variable declaration in one step
if (animal instanceof Dog dog) {
dog.fetch();
} else if (animal instanceof Cat cat) {
cat.scratch();
} else if (animal instanceof Bird bird) {
bird.fly();
}
}
public static void main(String[] args) {
Animal[] animals = {
new Dog("Buddy"),
new Cat("Whiskers"),
new Bird("Tweety")
};
for (Animal animal : animals) {
System.out.println("--- " + animal.name + " ---");
handleAnimalModern(animal);
}
// Incorrect downcasting causes ClassCastException
// Animal a = new Cat("Whiskers");
// Dog d = (Dog) a; // ClassCastException!
}
}
Designing with Polymorphism
A practical example of designing a payment system with polymorphism.
class Payment {
String payerName;
long amount;
Payment(String payerName, long amount) {
this.payerName = payerName;
this.amount = amount;
}
boolean process() {
System.out.println("Processing default payment");
return true;
}
void printReceipt() {
System.out.println("--- Receipt ---");
System.out.println("Payer: " + payerName);
System.out.println("Amount: " + String.format("%,d", amount) + " won");
}
}
class CreditCardPayment extends Payment {
String cardNumber;
CreditCardPayment(String payerName, long amount, String cardNumber) {
super(payerName, amount);
this.cardNumber = cardNumber;
}
@Override
boolean process() {
String masked = "****-****-****-" + cardNumber.substring(cardNumber.length() - 4);
System.out.println("Credit card payment: " + masked);
return true;
}
}
class BankTransfer extends Payment {
String bankName;
BankTransfer(String payerName, long amount, String bankName) {
super(payerName, amount);
this.bankName = bankName;
}
@Override
boolean process() {
System.out.println(bankName + " bank transfer processing...");
return true;
}
}
class MobilePayment extends Payment {
String phoneNumber;
MobilePayment(String payerName, long amount, String phoneNumber) {
super(payerName, amount);
this.phoneNumber = phoneNumber;
}
@Override
boolean process() {
System.out.println("Mobile payment (Phone: " + phoneNumber + ")");
return true;
}
}
public class PaymentSystem {
// Thanks to polymorphism, the same code handles any payment method
static void processPayment(Payment payment) {
if (payment.process()) {
payment.printReceipt();
System.out.println("Payment complete!\n");
}
}
public static void main(String[] args) {
Payment[] payments = {
new CreditCardPayment("Alice", 50000, "1234-5678-9012-3456"),
new BankTransfer("Bob", 100000, "Kookmin Bank"),
new MobilePayment("Charlie", 15000, "010-1234-5678")
};
for (Payment payment : payments) {
processPayment(payment); // All handled with the same method
}
}
}
Sealed Classes (Java 17+)
A feature that restricts which classes can inherit from a given class.
// sealed: only permitted subclasses can extend
sealed class Notification permits EmailNotification, SmsNotification, PushNotification {
String recipient;
String message;
Notification(String recipient, String message) {
this.recipient = recipient;
this.message = message;
}
void send() {
System.out.println("Sending notification: " + message);
}
}
final class EmailNotification extends Notification {
String subject;
EmailNotification(String recipient, String message, String subject) {
super(recipient, message);
this.subject = subject;
}
@Override
void send() {
System.out.println("Email sent -> " + recipient + " [" + subject + "]");
}
}
final class SmsNotification extends Notification {
SmsNotification(String recipient, String message) {
super(recipient, message);
}
@Override
void send() {
System.out.println("SMS sent -> " + recipient);
}
}
non-sealed class PushNotification extends Notification {
PushNotification(String recipient, String message) {
super(recipient, message);
}
@Override
void send() {
System.out.println("Push notification -> " + recipient);
}
}
Today’s Exercises
-
Shape Calculator: Store
Circle,Rectangle, andTriangleobjects in aShapearray, and use a loop to print each shape’s name and area. Use polymorphism to process them without type-specific branching. -
Discount Policy System: Create a
DiscountPolicyparent class and implementPercentDiscount(percentage discount),FixedDiscount(fixed amount discount), andNoDiscountas child classes. HandleapplyDiscount(long price)polymorphically. -
Zoo Simulation: Store various animal objects in an array and use
instanceof(pattern matching) to execute type-specific special behaviors for each animal. Also print each animal’s food type and quantity.